﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using XRoute.PathFinding;

namespace XRoute.Server.Model
{
    class Map
    {
        ///////////////////////////////////////////////////////////////////////

        readonly int _rows;
        readonly int _columns;
        readonly Node[] _nodes;


        ///////////////////////////////////////////////////////////////////////

        public Map(int rows, int columns, Node[] nodes)
        {
            Contract.Requires(nodes != null);
            Contract.Requires(rows > 0);
            Contract.Requires(columns > 0);
            Contract.Requires(nodes.Length == rows * columns);

            _rows = rows;
            _columns = columns;
            _nodes = nodes;
        }

        public int Rows
        {
            get { return _rows; }
        }

        public int Columns
        {
            get { return _columns; }
        }

        public Node this[int x, int y]
        {
            get
            {
                Contract.Requires(x >= 0 && x < Columns);
                Contract.Requires(y >= 0 && y < Rows);

                return _nodes[y * _columns + x];
            }
        }

        public Route FindRoute(Node origin, Node destination)
        {
            Contract.Requires(origin != null);
            Contract.Requires(destination != null);

            var path = Path<Node>.FindPath(origin, destination,
                distance: CalculateEdgeWeight,
                estimate: node => CalculateDistance(node, destination),
                neighbours: EnumerateNeighbours);

            return path != null
                   ? new Route(path)
                   : null;
        }


        ///////////////////////////////////////////////////////////////////////

        static double CalculateDistance(Node node1, Node node2)
        {
            var dx = (double)(node1.X - node2.X);
            var dy = (double)(node1.Y - node2.Y);

            return Math.Sqrt(dx * dx + dy * dy);
        }

        const double MaxNodeWeight = 50.0;

        static double CalculateEdgeWeight(Path<Node> originPath, Node destination)
        {
            var origin = originPath.Head;
            var distance = CalculateDistance(origin, destination);

            // punish diagonals
            if (destination.X != origin.X && destination.Y != origin.Y)
                distance += 0;// 1;

            // punish changes of direction
            if (originPath.Tail != null)
            {
                var previous = originPath.Tail.Head;
                var dx1 = previous.X - origin.X;
                var dy1 = previous.Y - origin.Y;
                var dx2 = origin.X - destination.X;
                var dy2 = origin.Y - destination.Y;

                if (dx1 != dx2 || dy1 != dy2)
                    distance += 0;// 3;
            }

            return distance + destination.Weight;
        }

        IEnumerable<Node> EnumerateNeighbours(Node node)
        {
            var firstX = Math.Max(node.X - 1, 0);
            var lastX = Math.Min(node.X + 1, _columns - 1);

            var firstY = Math.Max(node.Y - 1, 0);
            var lastY = Math.Min(node.Y + 1, _rows - 1);

            var lineBase = firstY * _columns;

            for (var y = firstY; y <= lastY; y++)
            {
                for (var x = firstX; x <= lastX; x++)
                    yield return _nodes[lineBase + x];

                lineBase += _columns;
            }
        }
    }
}
